﻿#include <string>
#include <iostream>
#include <cmath>


// For terminal delay
#include <chrono>
#include <thread>

#include <fstream>
#include <algorithm>

#include "game.h"


Game::Game()
{
    // Initialize SDL with all subsystems
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        // Handle initialization error
        std::cerr << "SDL initialization failed: " << SDL_GetError() << std::endl;
    }

    if (TTF_Init() < 0)
    {
        // Handle initialization error
        std::cerr << "TTF initialization failed: " << SDL_GetError() << std::endl;
    }
    

    // Create mWindows vector and resize it to 6
    this->mWindows.resize(6);
 
    // Get screen and board parameters
    SDL_GetDesktopDisplayMode(0, &DM);
    
    this->mScreenWidth = DM.w;
    this->mScreenHeight = DM.h;
    this->mGameBoardWidth = mScreenWidth * 0.4;
    this->mGameBoardHeight = mScreenWidth * 0.4;
    this->mInstructionWidth = mScreenWidth * 0.3;
    this->mInstructionHeight = mScreenWidth * 0.2;
    this->mInformationWidth = mScreenWidth * 0.3;
    this->mInformationHeight = mScreenWidth * 0.2;
    this->mScoreBoardWidth = mScreenWidth * 0.18;
    this->mScoreBoardHeight = mScreenWidth * 0.35;
    this->mInstructionBoardWidth = mScreenWidth * 0.4;
    this->mInstructionBoardHeight = mScreenWidth * 0.2;
    this->mPauseBoardWidth = mGameBoardWidth * 0.6;
    this->mPauseBoardHeight = mGameBoardHeight * 0.4;
    this->mShopBoardWidth = mScreenWidth * 0.45;
    this->mShopBoardHeight = mScreenWidth * 0.3;

    this->mScoreBoard.assign(this->mNumLeaders, 0);
    this->readScoreBoard();
    this->GameTheme.readTheme();  

    // Initialize the leader board to be all zeros
}

Game::~Game()
{
    for (int i = 0; i < this->mWindows.size(); i++)
    {
        SDL_DestroyWindow(this->mWindows[i]);  //Delete the windows
    }
    SDL_Quit();  //Quit the game
    TTF_Quit();
}

void Game::create_renderLastWindow(int lastWindowIndex) {
    if (lastWindowIndex == 0) {
        this->createInformationBoard();
        this->renderInformationBoard();
    }
    else if (lastWindowIndex == 1) {
        this->createScoreBoard();
        this->renderScoreBoard();
    }
    else if (lastWindowIndex == 4) {
        this->createPauseBoard();
        this->renderPauseBoard();
    }
    else if (lastWindowIndex == 5) {
        this->createShopBoard();
        this->renderShopBoard();
    }
}
//Create Information Board
void Game::createInformationBoard()
{
    this->mWindows[0] = SDL_CreateWindow("Information Board", (mScreenWidth - this->mInformationWidth) / 2, (mScreenHeight - this->mInformationHeight) / 2, this->mInformationWidth, this->mInformationHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[0] == nullptr) {
        return;
    }
}

//Render the information board (starting menu)
void Game::InformationBoardKeyboard(int selectedOption, std::vector<SDL_Texture*> InformationBoardKey, Renderer renderer)
{
    if (selectedOption == 1) {
        renderer.RenderTexture(InformationBoardKey[0], 0.07, 0.7, 0.25, 0.25);
    }
    else if (selectedOption == 2) {
        renderer.RenderTexture(InformationBoardKey[2], 0.38, 0.7, 0.25, 0.25);
    }
    else if (selectedOption == 3) {
        renderer.RenderTexture(InformationBoardKey[4], 0.7, 0.7, 0.25, 0.25);
    }
}

void Game::renderInformationBoard()
{
    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[0], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    Renderer renderer = Renderer(this->mWindows[0], mRenderer);

    //load information board texture
    std::vector<SDL_Texture*> InformationBoardBG;
    for (int i = 1; i <= 10; i++)  loadTexture("./source/start_menu/boa_" + std::to_string(i) + ".bmp", InformationBoardBG, renderer);
    std::vector<SDL_Texture*> InformationBoardKey;
    loadTexture("./source/start_menu/start.bmp", InformationBoardKey, renderer);
    loadTexture("./source/start_menu/start_ori.bmp", InformationBoardKey, renderer);
    loadTexture("./source/start_menu/score.bmp", InformationBoardKey, renderer);
    loadTexture("./source/start_menu/score_ori.bmp", InformationBoardKey, renderer);
    loadTexture("./source/start_menu/quit.bmp", InformationBoardKey, renderer);
    loadTexture("./source/start_menu/quit_ori.bmp", InformationBoardKey, renderer);

    int currentBackgroundIndex = 0;
    int selectedOption = 1;
    Uint32 lastTime = SDL_GetTicks();
    const int frameDelay = 150;
    Uint32 currentTime;
    int elapsedTime;
    bool Running = true;

    SDL_Texture* backBuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mInformationWidth, mInformationHeight);
    SDL_Event event;
    while (Running) {
        currentTime = SDL_GetTicks();
        elapsedTime = currentTime - lastTime;

        //Render at back stage
        SDL_SetRenderTarget(mRenderer, backBuffer);
        renderer.RenderTexture(InformationBoardBG[currentBackgroundIndex], 0, 0, 1, 1);
        renderer.RenderTexture(InformationBoardKey[1], 0.07, 0.7, 0.25, 0.25);
        renderer.RenderTexture(InformationBoardKey[3], 0.38, 0.7, 0.25, 0.25);
        renderer.RenderTexture(InformationBoardKey[5], 0.7, 0.7, 0.25, 0.25);
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                std::cout << "Goodbye!" << std::endl;
                SDL_DestroyWindow(this->mWindows[0]);
                SDL_QUIT;
                break;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_a:
                    std::cout << "Left key is pressed" << std::endl;
                    selectedOption = (selectedOption + 1) % 3 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_d:
                    selectedOption = selectedOption % 3 + 1;
                    std::cout << "Right key is pressed" << std::endl;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    SDL_DestroyWindow(this->mWindows[0]);
                    if (selectedOption == 1) {
                        this->windowTree.push_back(0);
                        this->createGameBoard();
                        this->renderGameBoard();
                        Running = false;
                        break;
                    }
                    else if (selectedOption == 2) {
                        this->windowTree.push_back(0);
                        this->createScoreBoard();
                        this->renderScoreBoard();
                        Running = false;
                        break;
                    }
                    else if (selectedOption == 3) {
                        Running = false;
                        SDL_QUIT;
                        break;
                    }
                }
            }
        }
        
        if (elapsedTime > frameDelay) {
            currentBackgroundIndex++;
            currentBackgroundIndex = currentBackgroundIndex % 10; //Change the index of background images
            lastTime = currentTime;
        }            
        
        InformationBoardKeyboard(selectedOption, InformationBoardKey, renderer);

        //Render at front stage (to avoid blink)
        SDL_SetRenderTarget(mRenderer, NULL);
        SDL_RenderCopy(mRenderer, backBuffer, NULL, NULL);
        SDL_RenderPresent(mRenderer);
        SDL_Delay(10);
    }
    SDL_DestroyRenderer(mRenderer);
    SDL_DestroyWindow(this->mWindows[0]);
}

//Create the score board
void Game::createScoreBoard() 
{
    this->mWindows[1] = SDL_CreateWindow("Score Board", (mScreenWidth - this->mScoreBoardWidth)/ 2, (mScreenHeight - this->mScoreBoardHeight) / 2, this->mScoreBoardWidth, this->mScoreBoardHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[1] == nullptr) {
        return;
    }
}
//Render the scores in the score board
void Game::renderScores(Renderer renderer)
{
    int windowWidth, windowHeight;
    SDL_GetWindowSize(renderer.getWindow(), &windowWidth, &windowHeight);

    SDL_Rect textRect = { windowWidth* 0.75, windowHeight * 0.21, windowWidth * 0.085, windowWidth * 0.13 };

    std::string pointString;
    //Render the text rim
    textRect.x = windowWidth * 0.74;
    textRect.y = windowHeight * 0.207;
    textRect.h = windowWidth * 0.14;
    textRect.w = windowWidth * 0.11;
    for (int i = 0; i < this->mNumLeaders; i++) {
        pointString = std::to_string(this->mScoreBoard[i]);
        if (this->mScoreBoard[i] >= 10) {
            textRect.x = windowWidth * 0.74 - int(log10(this->mScoreBoard[i])) * windowWidth * 0.085;
            textRect.w = windowWidth * 0.1 + int(log10(this->mScoreBoard[i])) * windowWidth * 0.085;
        }
        else {
            textRect.x = windowWidth * 0.74;
            textRect.w = windowWidth * 0.11;
        }
        textRect.y += windowHeight * 0.108;
        renderer.RenderText(textRect, pointString, 0, 0, 0, 255);
    }
    //Render the text fill
    textRect.x = windowWidth * 0.75;
    textRect.y = windowHeight * 0.21;
    textRect.h = windowWidth * 0.13;
    textRect.w = windowWidth * 0.085;
    for (int i = 0; i < this->mNumLeaders; i++) {
        pointString = std::to_string(this->mScoreBoard[i]);
        if (this->mScoreBoard[i] >= 10) {
            textRect.x = windowWidth * 0.75 - int(log10(this->mScoreBoard[i])) * windowWidth * 0.085;
            textRect.w = windowWidth * 0.085 + int(log10(this->mScoreBoard[i])) * windowWidth * 0.085;
        }
        else {
            textRect.x = windowWidth * 0.75;
            textRect.w = windowWidth * 0.085;
        }
        textRect.y += windowHeight * 0.108;
        renderer.RenderText(textRect, pointString, 255, 255, 255, 255);
    }
    SDL_RenderPresent(renderer.getRenderer());
}
//Render the score board
void Game::renderScoreBoard() 
{
    int currentBackgroundIndex = 1;
    int indexIncrement = 1;
    int selectedOption = 1;
    bool Running = true;
    Uint32 lastTime = SDL_GetTicks();
    const int frameDelay = 150;
    Uint32 currentTime;
    int elapsedTime;
    bool ScoreRunning = true;

    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[1], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    Renderer renderer = Renderer(this->mWindows[1], mRenderer);

    //load score board textures
    std::vector<SDL_Texture*> ScoreBoardBG;
    for (int i = 1; i <= 7; i++)  loadTexture("./source/score_board/score_" + std::to_string(i) + ".bmp", ScoreBoardBG, renderer);
    std::vector<SDL_Texture*> ScoreBoardKey;
    loadTexture("./source/score_board/back.bmp", ScoreBoardKey, renderer);
    loadTexture("./source/score_board/back_ori.bmp", ScoreBoardKey, renderer);
    loadTexture("./source/help.bmp", ScoreBoardKey, renderer);
    loadTexture("./source/help_ori.bmp", ScoreBoardKey, renderer);

    SDL_Texture* backBuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mScoreBoardWidth, mScoreBoardHeight);
    SDL_Event event;
    SDL_SetRenderTarget(mRenderer, backBuffer);
    while (ScoreRunning) {
        currentTime = SDL_GetTicks();
        elapsedTime = currentTime - lastTime;

        //Render at back stage
        SDL_SetRenderTarget(mRenderer, backBuffer);
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                SDL_DestroyWindow(this->mWindows[1]);
                int lastWindow = this->windowTree[windowTree.size() - 1];
                this->windowTree[windowTree.size() - 1] = 1;
                this->create_renderLastWindow(lastWindow);
                break;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_a:
                    std::cout << "Left key is pressed" << std::endl;
                    selectedOption = selectedOption % 2 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_d:
                    selectedOption = selectedOption % 2 + 1;
                    std::cout << "Right key is pressed" << std::endl;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    ScoreRunning = false;
                    SDL_DestroyWindow(this->mWindows[1]);
                    if (selectedOption == 1) {
                        int lastWindow = this->windowTree[windowTree.size() - 1];
                        this->windowTree[windowTree.size() - 1] = 1;
                        this->create_renderLastWindow(lastWindow);
                        break;
                    }
                    else if (selectedOption == 2) {
                        this->windowTree.push_back(1);
                        this->createInstructionBoard();
                        this->renderInstructionBoard();
                        break;
                    }
                };
            }
        }

        if (elapsedTime > frameDelay) {
            currentBackgroundIndex += indexIncrement;

            if (currentBackgroundIndex >= 6) {
                currentBackgroundIndex = 6;
                indexIncrement = -1; 
            }
            else if (currentBackgroundIndex <= 0) {
                currentBackgroundIndex = 0;
                indexIncrement = 1;  
            }
            lastTime = currentTime;
        }
        renderer.RenderTexture(ScoreBoardBG[currentBackgroundIndex], 0, 0, 1, 1);
        if (selectedOption == 1) {
            renderer.RenderTexture(ScoreBoardKey[0], 0.07, 0.87, 0.2 * (3 / 1.8), 0.2 * (2 / 3.5));
            renderer.RenderTexture(ScoreBoardKey[3], 0.6, 0.87, 0.2 * (3 / 1.8), 0.2 * (2 / 3.5));
        }
        else if (selectedOption == 2) {
            renderer.RenderTexture(ScoreBoardKey[1], 0.07, 0.87, 0.2 * (3 / 1.8), 0.2 * (2 / 3.5));
            renderer.RenderTexture(ScoreBoardKey[2], 0.6, 0.87, 0.2 * (3 / 1.8), 0.2 * (2 / 3.5));
        }
        renderScores(renderer);

        //Render at front stage (to avoid blink)
        SDL_SetRenderTarget(mRenderer, NULL);
        SDL_RenderCopy(mRenderer, backBuffer, NULL, NULL);
        SDL_RenderPresent(mRenderer);
        SDL_Delay(10);
    }
    SDL_DestroyRenderer(mRenderer);
    SDL_DestroyWindow(this->mWindows[1]);
}

//Create the instruction board
void Game::createInstructionBoard()
{
    this->mWindows[2] = SDL_CreateWindow("Instruction Board", (mScreenWidth - this->mInstructionBoardWidth) / 3, (mScreenHeight - this->mInstructionBoardHeight) / 3, this->mInstructionBoardWidth, this->mInstructionBoardHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[2] == nullptr) {
        return;
    }
}

//Render the instruction board
void Game::renderInstructionBoard()
{
    int windowWidth, windowHeight;
    windowWidth = this->mInstructionBoardWidth;
    windowHeight = this->mInstructionBoardHeight;

    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[2], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    SDL_Color rectColor = this->GameTheme.getInstructionColor();
    Renderer renderer = Renderer(this->mWindows[2], mRenderer);

    renderer.RenderRect( { 0, 0, windowWidth, windowHeight }, rectColor.r, rectColor.g, rectColor.b, 255);
    this->font = TTF_OpenFont("./source/fonts/bitlight10.ttf", 16);
    if (this->font == nullptr) {
        std::cerr << "Failed to load font " << std::endl;
        return;
    }

    SDL_Color textColor = { 0, 0, 0 };
    SDL_Texture* textTexture;
    SDL_Rect textRect = { windowWidth * 0.02, windowHeight * 0.1, windowWidth * 0.95, windowHeight * 0.08 };

    std::vector<std::string> instruction = {
        "Welcome to BOA : snake game!                                                                       ",
        "In this game, you should use your keyboard(WASD) to control the snake.    ",
        "You will be rewarded with scores when you eat a fruit.                                   ",
        "However, you must be careful with the barriers.                                              ",
        "The game will get more difficulty gradually.                                                     ",
        "When you hit a certain score, you can unlock a surprise.                                 ",
        "Have fun!                                                              ---->Press any key to return"
    };
    for (int i = 0; i < instruction.size(); i++) {
        renderer.RenderText(textRect, instruction[i].c_str(), 255, 255, 255, 255);
        textRect.y += windowHeight * 0.11;
    }
    SDL_RenderPresent(mRenderer);

    SDL_Event event;
    bool instructionRunning = true;
    while (instructionRunning) {
        if (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
            case SDL_KEYDOWN:
                SDL_DestroyWindow(this->mWindows[2]);
                int lastWindow = this->windowTree[windowTree.size() - 1];
                this->windowTree.pop_back();
                if (lastWindow != 4) this->create_renderLastWindow(lastWindow);
                instructionRunning = false;
                break;
            };
        }
        SDL_Delay(10);
     }
    SDL_DestroyRenderer(mRenderer);
}

bool Game::readScoreBoard()
{
    std::fstream fhand(this->mRecordBoardFilePath, fhand.binary | fhand.in);
    if (!fhand.is_open())
    {
        return false;
    }
    int temp;
    int i = 0;
    while ((!fhand.eof()) && (i < mNumLeaders))
    {
        fhand.read(reinterpret_cast<char*>(&temp), sizeof(temp));
        this->mScoreBoard[i] = temp;
        i++;
    }
    fhand.close();
    return true;
}

bool Game::updateScoreBoard()
{
    bool updated = false;
    int newScore = this->mPoints;
    for (int i = 0; i < this->mNumLeaders; i++)
    {
        if (this->mScoreBoard[i] >= this->mPoints)
        {
            continue;
        }
        int oldScore = this->mScoreBoard[i];
        this->mScoreBoard[i] = newScore;
        newScore = oldScore;
        updated = true;
    }
    return updated;
}

bool Game::writeScoreBoard()
{
    // trunc: clear the data file
    std::fstream fhand(this->mRecordBoardFilePath, fhand.binary | fhand.trunc | fhand.out);
    if (!fhand.is_open())
    {
        return false;
    }
    for (int i = 0; i < this->mNumLeaders; i++)
    {
        fhand.write(reinterpret_cast<char*>(&this->mScoreBoard[i]), sizeof(this->mScoreBoard[i]));;
    }
    fhand.close();
    return true;
}

void Game::createGameBoard() 
{
    this->mWindows[3] = SDL_CreateWindow("Game Board", (mScreenWidth - this->mGameBoardWidth) / 2, (mScreenHeight - this->mGameBoardHeight) / 2, this->mGameBoardWidth, this->mGameBoardHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[3] == nullptr) {
        return;
    }
}

void Game::renderGameBoard()
{   
    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[3], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    Renderer GameRenderer = Renderer(this->mWindows[3], mRenderer);
    this->chooseMap(GameRenderer);
    this->initializeGame(GameRenderer);
    this->renderCountdown(GameRenderer);
    this->runGame(GameRenderer);
}

void Game::renderCountdown(Renderer renderer)
{
    Uint8 Alpha = 255;
    int windowWidth = this->mGameBoardWidth;
    int windowHeight = this->mGameBoardHeight;
    int rect_w = windowWidth * 0.6;
    int rect_h = windowHeight * 0.4;
    int textWidth;
    int textHeight;
    std::string index;
    SDL_Color rectColor = this->GameTheme.getCountdownRect();
    SDL_Color textColor = this->GameTheme.getCountdownText();

    for (int i = 0; i <= 3; i++) {
        SDL_Rect rect = { (windowWidth - rect_w) / 2, (windowHeight - rect_h) / 2, rect_w, rect_h };
        Alpha = 255;
        SDL_Texture* texture = SDL_CreateTexture(renderer.getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, windowWidth, windowHeight);
        while (static_cast<int>(Alpha) > 30) {
            if (i != 3) {
                index = std::to_string(3 - i);
                textWidth = rect.w / 5;
            }
            else {
                index = "Go!";
                textWidth = rect.w / 2;
            }
            textHeight = rect.h / 2;

            SDL_SetRenderTarget(renderer.getRenderer(), texture);
            renderer.RenderTexture(TextureMap["GameBoardBG"][0], 0, 0, 1, 1);
            renderer.RenderRect(rect, rectColor.r, rectColor.g, rectColor.b, Alpha);
            renderer.RenderText( { (windowWidth - textWidth) / 2, (windowHeight - textHeight) / 2, textWidth , textHeight }, index.c_str(), textColor.r, textColor.g, textColor.b, Alpha);
            rect.w = 0.8 * rect.w;
            rect.h = 0.8 * rect.h;
            rect.x = (windowWidth - rect.w) / 2;
            rect.y = (windowHeight - rect.h) / 2;
            Alpha /= 2;
            SDL_SetRenderTarget(renderer.getRenderer(), NULL);
            SDL_RenderCopy(renderer.getRenderer(), texture, NULL, NULL);
            SDL_RenderPresent(renderer.getRenderer());
            SDL_Delay(200);
        }
    }
}

void Game::createPauseBoard()
{
    this->mWindows[4] = SDL_CreateWindow("Pause Board", (mScreenWidth - this->mPauseBoardWidth) / 2, (mScreenHeight - this->mPauseBoardHeight) / 2, this->mPauseBoardWidth, this->mPauseBoardHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[4] == nullptr) {
        return;
    }
}

void Game::PauseBoardKeyboard(int selectedOption, std::vector<SDL_Texture* > PauseBoard, Renderer renderer)
{
    std::string path = "./source/game_board/bg";
    std::string index = std::to_string(this->GameTheme.getGameBoardBG());
    if (selectedOption == 1) {
        renderer.RenderTexture(PauseBoard[2], 0.08, 0.1, 0.15, 0.225);
    }
    else if (selectedOption == 2) {
        renderer.RenderTexture(PauseBoard[4], 0.08, 0.4, 0.15, 0.225);
    }
    else if (selectedOption == 3) {
        renderer.RenderTexture(PauseBoard[6], 0.08, 0.7, 0.15, 0.225);
    }
    else if (selectedOption == 4) {
        renderer.RenderTexture(PauseBoard[8], 0.36, 0.37, 0.25, 0.375);
    }
    else if (selectedOption == 5) {
        renderer.RenderTexture(PauseBoard[10], 0.75, 0.2, 0.16, 0.25);
    }
    else if (selectedOption == 6) {
        renderer.RenderTexture(PauseBoard[12], 0.75, 0.6, 0.16, 0.25);
    }
}

void Game::renderPauseBoard()
{
    std::string path = "./source/game_board/bg";
    std::string index = std::to_string(this->GameTheme.getGameBoardBG());
    int selectedOption = 1;
    bool Running = true;

    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[4], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    Renderer renderer = Renderer(this->mWindows[4], mRenderer);

    //load pause board textures
    std::vector<SDL_Texture* > PauseBoard;
    loadTexture(path + "_" + index + "/pause_bg.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/restart_ori.bmp", PauseBoard, renderer); loadTexture(path + "_" + index + "/restart.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/mute_ori.bmp", PauseBoard, renderer); loadTexture(path + "_" + index + "/mute.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/shop_ori.bmp", PauseBoard, renderer); loadTexture(path + "_" + index + "/shop.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/play_ori.bmp", PauseBoard, renderer); loadTexture(path + "_" + index + "/play.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/help_ori.bmp", PauseBoard, renderer);  loadTexture(path + "_" + index + "/help.bmp", PauseBoard, renderer);
    loadTexture(path + "_" + "1" + "/home_ori.bmp", PauseBoard, renderer); loadTexture(path + "_" + index + "/home.bmp", PauseBoard, renderer);

    SDL_Texture* backBuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mPauseBoardWidth, mPauseBoardHeight);
    SDL_Event event;
    renderer.RenderTexture(PauseBoard[0], 0, 0, 1, 1);
    while (Running) {
        //Render at back stage
        SDL_SetRenderTarget(mRenderer, backBuffer);
        renderer.RenderTexture(PauseBoard[0], 0, 0, 1, 1);
        renderer.RenderTexture(PauseBoard[1], 0.08, 0.1, 0.15, 0.225);
        renderer.RenderTexture(PauseBoard[3], 0.08, 0.4, 0.15, 0.225);
        renderer.RenderTexture(PauseBoard[5], 0.08, 0.7, 0.15, 0.225);
        renderer.RenderTexture(PauseBoard[7], 0.36, 0.37, 0.25, 0.375);
        renderer.RenderTexture(PauseBoard[9], 0.75, 0.2, 0.16, 0.25);
        renderer.RenderTexture(PauseBoard[11], 0.75, 0.6, 0.16, 0.25);

        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                SDL_DestroyWindow(this->mWindows[4]);
                GamePause = false;
                Running = false;
                break;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_UP:
                case SDLK_a:
                case SDLK_w:
                    std::cout << "Left/Up key is pressed" << std::endl;
                    selectedOption = (selectedOption + 4) % 6 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_DOWN:
                case SDLK_d:
                case SDLK_s:
                    selectedOption = selectedOption % 6 + 1;
                    std::cout << "Right/Down key is pressed" << std::endl;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    if (selectedOption == 1) {
                        //restart
                        SDL_DestroyWindow(this->mWindows[4]);
                        SDL_DestroyWindow(this->mWindows[3]);
                        this->createGameBoard();
                        this->renderGameBoard();
                        Running = false;
                        break;
                    }
                    else if (selectedOption == 2) {
                        //mute
                    }
                    else if (selectedOption == 3) {
                        SDL_DestroyWindow(this->mWindows[4]);
                        this->windowTree[windowTree.size() - 1] = 4;
                        Running = false;
                        this->createShopBoard();
                        this->renderShopBoard();
                    }
                    else if (selectedOption == 4) {
                        GamePause = false;
                        Running = false;
                        SDL_DestroyWindow(this->mWindows[4]);
                        break;
                    }
                    else if (selectedOption == 5) {
                        this->windowTree.push_back(4);
                        this->createInstructionBoard();
                        this->renderInstructionBoard();
                        break;
                    }
                    else if (selectedOption == 6) {
                        //game end
                        SDL_DestroyWindow(this->mWindows[4]);
                        SDL_DestroyWindow(this->mWindows[3]);
                        this->endGame();
                        this->createInformationBoard();
                        this->renderInformationBoard();
                        Running = false;
                        break;
                    }
                }
            }
        }
        PauseBoardKeyboard(selectedOption, PauseBoard, renderer);
        //Render at front stage (to avoid blink)
        SDL_SetRenderTarget(mRenderer, NULL);
        SDL_RenderCopy(mRenderer, backBuffer, NULL, NULL);
        SDL_RenderPresent(mRenderer);
        SDL_Delay(10);
    }
    SDL_DestroyRenderer(mRenderer);
    SDL_DestroyWindow(this->mWindows[4]);
}

void Game::createShopBoard()
{
    this->mWindows[5] = SDL_CreateWindow("Shop Board", (mScreenWidth - this->mShopBoardWidth) / 2, (mScreenHeight - this->mShopBoardHeight) / 2, this->mShopBoardWidth, this->mShopBoardHeight, SDL_WINDOW_SHOWN);
    if (this->mWindows[5] == nullptr) {
        return;
    }
}

void Game::ShopBoardKeyboard(bool area, int selectedOption, std::vector<SDL_Texture*> ShopBoard, Renderer renderer)
{
    if (area && selectedOption == 1) {
        renderer.RenderTexture(ShopBoard[2], 0.18, 0.25, 0.26, 0.52);
    }
    else if (area && selectedOption == 2) {
        renderer.RenderTexture(ShopBoard[4], 0.53, 0.25, 0.26, 0.52);
    }
    else if (!area && selectedOption == 1) {
        renderer.RenderTexture(ShopBoard[6], 0.04, 0.87, 0.07, 0.1);
    }
    else if (selectedOption == 2) {
        renderer.RenderTexture(ShopBoard[8], 0.13, 0.87, 0.07, 0.1);
    }
    else if (selectedOption == 3) {
        renderer.RenderTexture(ShopBoard[10], 0.22, 0.87, 0.07, 0.1);
    }
}

void Game::renderShopBoard()
{
    int selectedOption = 1;
    bool area = 1;
    bool Running = true;

    SDL_Renderer* mRenderer = SDL_CreateRenderer(this->mWindows[5], -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    Renderer renderer = Renderer(this->mWindows[5], mRenderer);

    //load shop board textures
    std::vector<SDL_Texture* > ShopBoard;
    loadTexture("./source/shop_board/store_bg.bmp", ShopBoard, renderer);
    loadTexture("./source/shop_board/theme1_ori.bmp", ShopBoard, renderer); loadTexture("./source/shop_board/theme1.bmp", ShopBoard, renderer);
    loadTexture("./source/shop_board/theme2_ori.bmp", ShopBoard, renderer); loadTexture("./source/shop_board/theme2.bmp", ShopBoard, renderer);
    loadTexture("./source/game_board/bg_1/back_ori.bmp", ShopBoard, renderer); loadTexture("./source/game_board/bg_1/back.bmp", ShopBoard, renderer);
    loadTexture("./source/game_board/bg_1/help_ori.bmp", ShopBoard, renderer); loadTexture("./source/game_board/bg_1/help.bmp", ShopBoard, renderer);
    loadTexture("./source/game_board/bg_1/home_ori.bmp", ShopBoard, renderer);  loadTexture("./source/game_board/bg_1/home.bmp", ShopBoard, renderer);
    loadTexture("./source/shop_board/lock.bmp", ShopBoard, renderer);
    loadTexture("./source/shop_board/money.bmp", ShopBoard, renderer);

    SDL_Texture* backBuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mShopBoardWidth, mShopBoardHeight);
    SDL_Event event;

    while (Running) {
        //Render at back stage
        SDL_SetRenderTarget(mRenderer, backBuffer);
        //Render themes
        renderer.RenderTexture(ShopBoard[0], 0, 0, 1, 1);
        renderer.RenderTexture(ShopBoard[1], 0.18, 0.25, 0.26, 0.52);
        renderer.RenderTexture(ShopBoard[3], 0.53, 0.25, 0.26, 0.52);
        //Render buttoms
        renderer.RenderTexture(ShopBoard[5], 0.04, 0.87, 0.07, 0.1);
        renderer.RenderTexture(ShopBoard[7], 0.13, 0.87, 0.07, 0.1);
        renderer.RenderTexture(ShopBoard[9], 0.22, 0.87, 0.07, 0.1);
        //Render lock & money (if unlocked?)
        renderer.RenderTexture(ShopBoard[11], 0.76, 0.2, 0.07, 0.1);
        renderer.RenderTexture(ShopBoard[12], 0.78, 0.025, 0.07, 0.1);
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                SDL_DestroyWindow(this->mWindows[5]);
                int lastWindow = this->windowTree[windowTree.size() - 1];
                this->windowTree[windowTree.size() - 1] = 5;
                this->create_renderLastWindow(lastWindow);
                break;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_a:
                    std::cout << "Left key is pressed" << std::endl;
                    if (area) selectedOption = (selectedOption) % 2 + 1;
                    else selectedOption = (selectedOption + 1) % 3 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_d:
                    if (area) selectedOption = selectedOption % 2 + 1;
                    else selectedOption = selectedOption % 3 + 1;
                    std::cout << "Right key is pressed" << std::endl;
                    break;
                case SDLK_UP:
                case SDLK_w:
                case SDLK_DOWN:
                case SDLK_s:
                    area = !area;
                    selectedOption = 1;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    if (area) {
                        if (selectedOption == 1) {
                            this->GameTheme.setTheme(1);
                            loadGameTexture(Renderer(this->mWindows[3], SDL_GetRenderer(this->mWindows[3])));
                            std::cout << "Theme 1 is set" << std::endl;
                            break;
                        }
                        else if (selectedOption == 2) {
                            this->GameTheme.setTheme(2);
                            loadGameTexture(Renderer(this->mWindows[3], SDL_GetRenderer(this->mWindows[3])));
                            std::cout << "Theme 2 is set" << std::endl;
                            break;
                        }
                    }
                    else {
                        if (selectedOption == 1) {
                            //back to last window
                            SDL_DestroyWindow(this->mWindows[5]);
                            int lastWindow = this->windowTree[windowTree.size() - 1];
                            this->windowTree[windowTree.size() - 1] = 5;
                            this->create_renderLastWindow(lastWindow);
                            Running = false;
                            break;
                        }
                        else if (selectedOption == 2) {
                            //help
                            SDL_DestroyWindow(this->mWindows[5]);
                            this->windowTree.push_back(5);
                            this->createInstructionBoard();
                            this->renderInstructionBoard();
                            Running = false;
                            break;
                        }
                        else if (selectedOption == 3) {
                            //back home
                            SDL_DestroyWindow(this->mWindows[5]);
                            SDL_DestroyWindow(this->mWindows[3]);
                            this->endGame();
                            this->createInformationBoard();
                            this->renderInformationBoard();
                            Running = false;
                            break;
                        }
                    }
                }
            }
        }
        ShopBoardKeyboard(area, selectedOption, ShopBoard, renderer);
        //Render at front stage (to avoid blink)
        SDL_SetRenderTarget(mRenderer, NULL);
        SDL_RenderCopy(mRenderer, backBuffer, NULL, NULL);
        SDL_RenderPresent(mRenderer);
        SDL_Delay(10);
    }
    SDL_DestroyRenderer(mRenderer);
    SDL_DestroyWindow(this->mWindows[5]);
}

void Game::GameKeyBoard(SDL_Event& event, bool& GameRunning)
{
    if (event.type == SDL_QUIT) {
        std::cout << "Goodbye!" << std::endl;
        SDL_DestroyWindow(this->mWindows[3]);
        SDL_Quit();
        GameRunning = !GameRunning;
    }
    else if (event.type == SDL_KEYDOWN) {
        switch (event.key.keysym.sym) {
        case SDLK_LEFT:
        case SDLK_UP:
        case SDLK_a:
        case SDLK_w:
        case SDLK_RIGHT:
        case SDLK_DOWN:
        case SDLK_d:
        case SDLK_s:
            this->controlSnake(event);
            break;
        case SDLK_SPACE:
        case SDLK_KP_ENTER:
        case SDLK_RETURN:
            std::cout << "Enter/Space key is pressed" << std::endl;
            this->GamePause = !GamePause;
            this->createPauseBoard();
            this->renderPauseBoard();
            break;
        };
    }
}

void Game::runGame(Renderer renderer)
{
    int selectedOption = 1;
    bool GameRunning = true;
    this->GamePause = false;
    int moveSuccess;

    SDL_Rect rect = { mMap["startX"] * this->mGameBoardWidth, mMap["startY"] * this->mGameBoardHeight,
        mMap["gridSize"] * mMap["colNums"] * this->mGameBoardHeight, mMap["gridSize"] * mMap["rowNums"] * this->mGameBoardWidth };
    SDL_Renderer* mRenderer = renderer.getRenderer();
    SDL_Texture* backBuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mGameBoardWidth, mGameBoardHeight);
    
    Uint32 lastTime = SDL_GetTicks();
    Uint32 lastTime2 = SDL_GetTicks();
    int frameDelay = 100;
    Uint32 currentTime;
    int elapsedTime;
    int elapsedTime2;
    SDL_Rect PointsRect = { mGameBoardWidth * 0.7, mGameBoardHeight * 0.1, mGameBoardWidth * 0.18, mGameBoardHeight * 0.05 };
    bool crazymode = false;

    while (GameRunning) {
        currentTime = SDL_GetTicks();
        elapsedTime = currentTime - lastTime;
        elapsedTime2 = currentTime - lastTime2;
        
        SDL_SetRenderTarget(mRenderer, backBuffer);

        SDL_Event event;

        //render background
        renderer.RenderTexture(TextureMap["GameBoardBG"][0], 0, 0, 1, 1);
        renderer.RenderRect(rect, this->GameTheme.getGameBoardColor().r, this->GameTheme.getGameBoardColor().g, this->GameTheme.getGameBoardColor().b, this->GameTheme.getGameBoardColor().a);
        
        //render barriers & snake & food
        this->renderBarriers(renderer);
        this->renderSnake(renderer);
        this->renderFood(renderer);
        if (mPoints >= 10)
        {
            PointsRect.x = mGameBoardWidth * 0.7 - mGameBoardWidth * int(log10(mPoints)) * 0.05;
            PointsRect.w = mGameBoardWidth * 0.15 + mGameBoardWidth * int(log10(mPoints)) * 0.05;
        }
        this->renderPoints(renderer, PointsRect, { 255,255,255,255 });
        if (!GamePause) {
            if (SDL_PollEvent(&event)) {
                GameKeyBoard(event, GameRunning);
            }
            //refresh the food if not eaten in 6 seconds
            if (elapsedTime2 >= 6000) {
                for (int i = 0; i < FoodNums; i++) this->DestroyFood(i, renderer);
                this->createRandomFood();
                this->renderFood(renderer);
                this->mPtrSnake->senseFood(this->mFoods);
                lastTime2 = currentTime;
            }
            //crazy mode: every time points hit a certain number, create many foods
            if (mPoints % 15 == 0 && mPoints != 0 && FoodNums <= 10) crazymode = true;
            if (crazymode) {
                for (int i = 0; i < 10; i++) this->createRandomFood();
                this->renderFood(renderer);
                this->mPtrSnake->senseFood(this->mFoods);
                lastTime2 = currentTime;
                crazymode = false;
            }
            //basic game logic
            if (elapsedTime >= frameDelay) {
                moveSuccess = this->mPtrSnake->moveFoward();
                if (this->mPtrSnake->checkCollision()) {
                    std::cout << "Game Over!" << std::endl;
                    break;
                }
                if (moveSuccess != -1) {
                    this->mPoints++;
                    this->DestroyFood(moveSuccess, renderer);
                    if (!crazymode && FoodNums == 0) this->createRandomFood();
                    this->renderFood(renderer);
                    this->mPtrSnake->senseFood(this->mFoods);
                    this->adjustDelay(frameDelay);
                    lastTime2 = currentTime;
                }
                lastTime = currentTime;
            }
        }
        SDL_SetRenderTarget(mRenderer, NULL);
        SDL_RenderCopy(mRenderer, backBuffer, NULL, NULL);
        SDL_RenderPresent(mRenderer);
        SDL_Delay(10);
    }
    int choice = this->restartGame(renderer, backBuffer);
    if ( choice == 1) {
        this->updateScoreBoard();
        this->writeScoreBoard();
        initializeGame(renderer);
        renderCountdown(renderer);
        runGame(renderer);
    }
    else if (choice == 2) {
        this->endGame();
        SDL_DestroyTexture(backBuffer);
        SDL_DestroyRenderer(mRenderer);
        SDL_DestroyWindow(this->mWindows[3]);
        this->createInformationBoard();
        this->renderInformationBoard();
    }
    else if (choice == 3) {
        this->endGame();
        SDL_DestroyTexture(backBuffer);
        SDL_DestroyRenderer(mRenderer);
        SDL_DestroyWindow(this->mWindows[3]);
        this->windowTree[windowTree.size() - 1] = 0;
        this->createScoreBoard();
        this->renderScoreBoard();
    }
    else {
        this->endGame();
    }
    SDL_DestroyTexture(backBuffer);
    SDL_DestroyRenderer(mRenderer);
    SDL_DestroyWindow(this->mWindows[3]);
    SDL_Quit();
}
int Game::restartGame(Renderer renderer, SDL_Texture* backBuffer)
{
    SDL_Rect RestartRect = { (mGameBoardWidth - mGameBoardWidth * 0.5) / 2  , (mGameBoardHeight - mGameBoardHeight * 0.3) / 2,  mGameBoardWidth * 0.5, mGameBoardHeight * 0.3 };
    SDL_Color RestartColor = GameTheme.getRestartColor();
    SDL_Color RestartOriColor = GameTheme.getRestartOriColor();
    SDL_Rect GameOverRect = { (mGameBoardWidth - RestartRect.w * 0.5) / 2, mGameBoardHeight* 0.4, RestartRect.w * 0.5, RestartRect.h * 0.2 };

    SDL_Rect RestartButtom = { mGameBoardWidth * 0.3, mGameBoardHeight * 0.55, RestartRect.w * 0.25, RestartRect.h * 0.2 };
    SDL_Rect RestartText = { mGameBoardWidth * 0.31, mGameBoardHeight * 0.565, RestartRect.w * 0.22, RestartRect.h * 0.11 };

    SDL_Rect HomeButtom = { mGameBoardWidth * 0.435, mGameBoardHeight * 0.55, RestartRect.w * 0.25, RestartRect.h * 0.2 };
    SDL_Rect HomeText = { mGameBoardWidth * 0.463, mGameBoardHeight * 0.565, RestartRect.w * 0.14, RestartRect.h * 0.11 };

    SDL_Rect ScoreButtom = { mGameBoardWidth * 0.57, mGameBoardHeight * 0.55, RestartRect.w * 0.25, RestartRect.h * 0.2 };
    SDL_Rect ScoreText = { mGameBoardWidth * 0.59, mGameBoardHeight * 0.565, RestartRect.w * 0.17, RestartRect.h * 0.11 };

    SDL_SetRenderTarget(renderer.getRenderer(), backBuffer);
    renderer.RenderRect(RestartRect, RestartColor.r, RestartColor.g, RestartColor.b, RestartColor.a);
    int selectedOption = 1;
    bool RestartRunning = true;

    SDL_Event event;
    while (RestartRunning) {
        SDL_SetRenderTarget(renderer.getRenderer(), backBuffer);
        //render restart menu
        renderer.RenderText(GameOverRect, "Game Over!", 255, 255, 255, 255);
        renderer.RenderRect(RestartButtom, RestartOriColor.r, RestartOriColor.g, RestartOriColor.b, 255);
        renderer.RenderRect(HomeButtom, RestartOriColor.r, RestartOriColor.g, RestartOriColor.b, 255);
        renderer.RenderRect(ScoreButtom, RestartOriColor.r, RestartOriColor.g, RestartOriColor.b, 255);

        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                std::cout << "Goodbye!" << std::endl;
                SDL_DestroyWindow(this->mWindows[3]);
                SDL_Quit();
                return 0;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_a:
                    std::cout << "Left key is pressed" << std::endl;
                    selectedOption = (selectedOption + 1) % 3 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_d:
                    selectedOption = selectedOption % 3 + 1;
                    std::cout << "Right key is pressed" << std::endl;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    if (selectedOption == 1) {
                        //restart
                        return 1;
                    }
                    else if (selectedOption == 2) {
                        //back home
                        return 2;
                    }
                    else if (selectedOption == 3) {
                        //score
                        return 3;
                    }
                }
            }
        }
        if (selectedOption == 1) {
            renderer.RenderRect(RestartButtom, RestartColor.r, RestartColor.g, RestartColor.b, 255);
        }
        else if (selectedOption == 2) {
            renderer.RenderRect(HomeButtom, RestartColor.r, RestartColor.g, RestartColor.b, 255);
        }
        else if (selectedOption == 3) {
            renderer.RenderRect(ScoreButtom, RestartColor.r, RestartColor.g, RestartColor.b, 255);
        }
        renderer.RenderText(RestartText, "Restart", 255, 255, 255, 255);
        renderer.RenderText(HomeText, "Home", 255, 255, 255, 255);
        renderer.RenderText(ScoreText, "Score", 255, 255, 255, 255);
        SDL_SetRenderTarget(renderer.getRenderer(), NULL);
        SDL_RenderCopy(renderer.getRenderer(), backBuffer, NULL, NULL);
        SDL_RenderPresent(renderer.getRenderer());
        SDL_Delay(10);
    }
    return false;
}

void Game::endGame()
{
    this->updateScoreBoard();
    this->writeScoreBoard();
}

void Game::renderPoints(Renderer renderer, SDL_Rect rect, SDL_Color placeColor)
{   
    std::string Points = "Points : " + std::to_string(this->mPoints);
    renderer.RenderText(rect, Points, placeColor.r, placeColor.g, placeColor.b, placeColor.a);
}

void Game::renderBarriers(Renderer renderer)
{
    for (int i = 0; i < int(mMap["barrierNums"]); i++) {
        renderer.RenderTexture(TextureMap["Barrier"][0], mMap["startX"] + mMap["gridSize"] * mBarriers[i].first,
            mMap["startX"] + mMap["gridSize"] * mBarriers[i].second, mMap["gridSize"], mMap["gridSize"]);
    }
}

void Game::controlSnake(SDL_Event event) const
{
    switch (event.key.keysym.sym) {
    case SDLK_w:
    case SDLK_UP:
        this->mPtrSnake->changeDirection(Direction::Up);
        std::cout << "Up" << std::endl;
        break;
    case SDLK_s:
    case SDLK_DOWN:
        this->mPtrSnake->changeDirection(Direction::Down);
        std::cout << "Down" << std::endl;
        break;
    case SDLK_a:
    case SDLK_LEFT:
        this->mPtrSnake->changeDirection(Direction::Left);
        std::cout << "Left" << std::endl;
        break;
    case SDLK_d:
    case SDLK_RIGHT:
        this->mPtrSnake->changeDirection(Direction::Right);
        std::cout << "Right" << std::endl;
        break;
    default:
        break;
    }
}

void Game::renderSnake(Renderer renderer)
{
    int snakeLength = this->mPtrSnake->getLength();
    std::vector<SnakeBody>& snake = this->mPtrSnake->getSnake();
    double snakeX, snakeY, degree;
    for (int i = 0; i < snakeLength; i++)
    {
        snakeX = mMap["startX"] + mMap["gridSize"] * snake[i].getX();
        snakeY = mMap["startX"] + mMap["gridSize"] * snake[i].getY();
        degree = this->DirectionMap[snake[i].getDirection()];
        if (i == 0) {
            renderer.renderRotatedTexture(TextureMap["Snake"][0], degree, snakeX, snakeY, mMap["gridSize"], mMap["gridSize"]);
        }
        else if (i == snakeLength - 1) {
            renderer.renderRotatedTexture(TextureMap["Snake"][2], degree, snakeX, snakeY, mMap["gridSize"], mMap["gridSize"]);
        }
        else {
            renderer.renderRotatedTexture(TextureMap["Snake"][1], degree, snakeX, snakeY, mMap["gridSize"], mMap["gridSize"]);
        }
    }
}

bool Game::isPartOfWall(const int x, const int y) const
{
    for (point wall : mBarriers) {
        if (wall.first == x && wall.second == y) {
            return true;
        }
    }
    return false;
}

void Game::createRandomFood()
{
    int x, y;
    int cols = mMap["colNums"];
    int rows = mMap["rowNums"];
    do {
        x = rand() % (cols - 2) + 1;
        y = rand() % (rows - 2) + 1;
    } while (this->mPtrSnake->isPartOfSnake(x, y) || isPartOfWall(x,y)); //Create a food at random places & make sure that the food doesn't overlap with the snake.
    this->mFoods.push_back(point(x, y));
    this->FoodNums++;
}

void Game::renderFood(Renderer renderer) 
{
    for (int i = 0; i < FoodNums; i++) {
        renderer.RenderTexture(TextureMap["Food"][0], mMap["startX"] + mMap["gridSize"] * mFoods[i].first,
            mMap["startX"] + mMap["gridSize"] * mFoods[i].second, mMap["gridSize"], mMap["gridSize"]);
    }
}

void Game::DestroyFood(int i, Renderer renderer) 
{
    this->mFoods.erase(mFoods.begin() + i);
    FoodNums--;
}

void Game::startGame() 
{
    this->createInformationBoard();
    this->renderInformationBoard();
}

void Game::adjustDelay(int& frameDelay)
{
    this->mDifficulty = this->mPoints / 5;
    if (mPoints == 0) {
        frameDelay = 100;
    }
    else
        if (mPoints % 5 == 0)
        {
            frameDelay = 100 * pow(0.8, this->mDifficulty);
        }
}

void Game::initializeGame(Renderer renderer)
{
    //initialize all the board information
    mMap = this->GameMap.getMap();
    mBarriers = this->GameMap.getBarriers();

    this->loadGameTexture(renderer);

    /* TODO*/
    this->mPoints = 0; //Initialize the game points as zero
    this->mFoods.clear();
    this->FoodNums = 0;
    // allocate memory for a new snake
    this->mPtrSnake.reset(new Snake(this->GameMap));
    this->createRandomFood(); //Create a food at random place
    this->renderFood(renderer);
    this->mPtrSnake->senseFood(this->mFoods); //Make the snake aware of the food
    this->readScoreBoard();
}

void Game::chooseMap(Renderer renderer) {
    int selectedOption = 1;
    bool ChooseRunning = true;
    int W = mGameBoardWidth;
    int H = mGameBoardHeight;

    SDL_Event event;
    SDL_Rect ChooseMap = { (W - W / 2) / 2, (H - H / 2) / 2, W / 2, H / 2 };
    SDL_Color Color = GameTheme.getGameBoardColor();
    SDL_Color ButtomColor = GameTheme.getRestartColor();
    SDL_Color ButtomOriColor = GameTheme.getRestartOriColor();

    std::vector<SDL_Texture*> BGTexture;
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/bg.bmp", BGTexture, renderer);

    renderer.RenderTexture(BGTexture[0], 0, 0, 1, 1);
    renderer.RenderRect(ChooseMap, Color.r, Color.g, Color.b, 100);
    
    SDL_Rect Rect_1 = { ChooseMap.x + ChooseMap.w * 0.1, ChooseMap.y + ChooseMap.h * 0.1, ChooseMap.w * 0.2,ChooseMap.h * 0.2 };
    SDL_Rect Rect_2 = { Rect_1.x + ChooseMap.w * 0.3, ChooseMap.y + ChooseMap.h * 0.1, Rect_1.w, Rect_1.h};
    SDL_Rect Rect_3 = { Rect_2.x + ChooseMap.w * 0.3, ChooseMap.y + ChooseMap.h * 0.1, Rect_1.w, Rect_1.h };
    std::vector<SDL_Rect> blockRect = {Rect_1, Rect_2, Rect_3};

    std::vector<SDL_Rect> textRect;
    for (int i = 0; i < 3; i++) {
        SDL_Rect temp = { blockRect[i].x + blockRect[i].w * 0.35, blockRect[i].y + blockRect[i].h * 0.29, blockRect[i].w * 0.4, blockRect[i].h * 0.6 };
        textRect.push_back(temp);
    }

    while (ChooseRunning) {
        //render map-choosing menu
        for (int i = 0; i < 3; i++) {
            renderer.RenderRect(blockRect[i], ButtomOriColor.r, ButtomOriColor.g, ButtomOriColor.b, 255);
        }
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                std::cout << "Goodbye!" << std::endl;
                ChooseRunning = false;
                SDL_DestroyWindow(this->mWindows[3]);
                SDL_Quit();
                break;
            }
            else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_a:
                    std::cout << "Left key is pressed" << std::endl;
                    selectedOption = (selectedOption + 1) % 3 + 1;
                    break;
                case SDLK_RIGHT:
                case SDLK_d:
                    selectedOption = selectedOption % 3 + 1;
                    std::cout << "Right key is pressed" << std::endl;
                    break;
                case SDLK_SPACE:
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    std::cout << "Enter/Space key is pressed" << std::endl;
                    if (selectedOption == 1) {
                        this->GameMap = Map("./data/map/map_1.txt");
                        return;
                    }
                    else if (selectedOption == 2) {
                        this->GameMap = Map("./data/map/map_2.txt");
                        return;
                    }
                    else if (selectedOption == 3) {
                        this->GameMap = Map("./data/map/map_3.txt");
                        return;
                    }
                }
            }
        }
        renderer.RenderRect(blockRect[selectedOption-1], ButtomColor.r, ButtomColor.g, ButtomColor.b, 255);
        for (int i = 0; i < 3; i++) {
            renderer.RenderText(textRect[i], std::to_string(i+1), 255, 255, 255, 255);
        }
        SDL_RenderPresent(renderer.getRenderer());
        SDL_Delay(10);
    }
}

//Load Textures so as to decrease the delay
void Game::loadTexture(std::string imagePath, std::vector<SDL_Texture*>& Textures, Renderer renderer)
{
    SDL_Surface* imageSurface = SDL_LoadBMP(imagePath.c_str());
    if (imageSurface == nullptr) {
        std::cerr << "Failed to load image: " << imagePath << std::endl;
        return;
    }
    SDL_Texture* imageTexture = SDL_CreateTextureFromSurface(renderer.getRenderer(), imageSurface);
    Textures.push_back(imageTexture);
}

void Game::loadGameTexture(Renderer renderer)
{
    //load barrier texture
    std::vector<SDL_Texture*> BarrierTexture;
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/wall.bmp", BarrierTexture, renderer);
    TextureMap["Barrier"] = BarrierTexture;

    //load game board background texture
    std::vector<SDL_Texture*> GameBoardBGTexture;
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/bg.bmp", GameBoardBGTexture, renderer);
    TextureMap["GameBoardBG"] = GameBoardBGTexture;

    //load snake textures
    std::vector<SDL_Texture*> SnakeTextures;
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/head.bmp", SnakeTextures, renderer);
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/body.bmp", SnakeTextures, renderer);
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/tail.bmp", SnakeTextures, renderer);
    TextureMap["Snake"] = SnakeTextures;

    //load food textures
    std::vector<SDL_Texture*> FoodTexture;
    loadTexture("./source/game_board/bg_" + std::to_string(this->GameTheme.getThemeindex()) + "/food.bmp", FoodTexture, renderer);
    TextureMap["Food"] = FoodTexture;
}